package com.yuandian.utils;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import com.yuandian.enums.MergeCalculateType;
import com.yuandian.po.MergeDataConfig;
import com.yuandian.po.MergeField;
import com.yuandian.po.ScaleSelfAdaptiveRule;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;

/**
 * 说明：数据归并工具类
 * @Author yanjun
 * @Date 2022/2/15 11:52
 */
public class MergeDataUtil {

	/**
	 * 动态刻度规则列表
	 */
    public static final List<ScaleSelfAdaptiveRule> scaleRuleList;

    static {
        scaleRuleList = new ArrayList<>();
        // 小于等于1分钟，刻度设置为1(1秒一个刻度)，刻度数量[0, 60]
        scaleRuleList.add(new ScaleSelfAdaptiveRule(0L, 60000L, 1));
        // 大于1分钟，小于等于1小时，刻度设置为60(1分钟一个刻度)，刻度数量[1, 60]
        scaleRuleList.add(new ScaleSelfAdaptiveRule(60000L, 3600000L, 60));
        // 大于1小时，小于等于3小时，刻度设置为300(5分钟一个刻度)，刻度数量[12, 36]
        scaleRuleList.add(new ScaleSelfAdaptiveRule(3600000L, 10800000L, 300));
        // 大于3小时，小于等于6小时，刻度设置为600(10分钟一个刻度)，刻度数量[18, 36]
        scaleRuleList.add(new ScaleSelfAdaptiveRule(10800000L, 21600000L, 600));
        // 大于6小时，小于等于12小时，刻度设置为900(15分钟一个刻度)，刻度数量[24, 48]
        scaleRuleList.add(new ScaleSelfAdaptiveRule(21600000L, 43200000L, 900));
        // 大于12小时，小于等于18小时，刻度设置为1200(20分钟一个刻度)，刻度数量[36, 54]
        scaleRuleList.add(new ScaleSelfAdaptiveRule(43200000L, 64800000L, 1200));
        // 大于18小时，小于等于24小时，刻度设置为1500(25分钟一个刻度)，刻度数量[43, 57]
        scaleRuleList.add(new ScaleSelfAdaptiveRule(64800000L, 86400000L, 1500));
        // 大于24小时，小于等于36小时，刻度设置为1800(30分钟一个刻度)，刻度数量[48, 72]
        scaleRuleList.add(new ScaleSelfAdaptiveRule(86400000L, 129600000L, 1800));
        // 大于36小时，小于等于48小时，刻度设置为2700(45分钟一个刻度)，刻度数量[48, 64]
        scaleRuleList.add(new ScaleSelfAdaptiveRule(129600000L, 172800000L, 2700));

        // 大于48小时，小于等于7天，刻度设置为3600(1小时一个刻度)，刻度数量[48, 168]
        scaleRuleList.add(new ScaleSelfAdaptiveRule(172800000L, 604800000L, 3600));
        // 大于7天，小于等于15天，刻度设置为7200(2小时一个刻度)，刻度数量[84, 180]
        scaleRuleList.add(new ScaleSelfAdaptiveRule(604800000L, 1296000000L, 7200));
        // 大于15天，小于等于30天，刻度设置为10800(3小时一个刻度)，刻度数量[120, 240]
        scaleRuleList.add(new ScaleSelfAdaptiveRule(1296000000L, 2592000000L, 10800));

        // 大于30天，刻度设置为86400(1天一个刻度)，刻度数量[30, +∞]
        scaleRuleList.add(new ScaleSelfAdaptiveRule(2592000000L, null, 86400));
    }

	private MergeDataUtil() {}

    /**
     * 归并数据
     * @param config 数据归并配置项
     * @return 归并后的数据集
     */
    public static Map<String, Object> mergeData(MergeDataConfig config) {
        // 1、校验参数，设置默认值
        Class<?> clazz = config.getClazz();
        List<?> data = config.getData();
        List<MergeField> mergeFieldList = config.getMergeFieldList();
        Integer scale = config.getScale();
        boolean scaleSelfAdaptive = false;
        String labelTimeFormatter = config.getLabelTimeFormatter();
        String cycleTimeKey = config.getCycleTimeKey();
        // 校验刻度值，如果没有设置或小于等于0，自动启用刻度自适应
        if (scale == null || scale <= 0) {
            scaleSelfAdaptive = true;
        }
        // 时间标签格式化，默认为yyyy-MM-dd HH:mm:ss
        if (StrUtil.isEmpty(labelTimeFormatter)) {
            labelTimeFormatter = "yyyy-MM-dd HH:mm:ss";
        }
        // 时间周期字段名，默认为cycleTime
        if (StrUtil.isEmpty(cycleTimeKey)) {
            cycleTimeKey = "cycleTime";
        }

        // 2、如果启动刻度自适应，则根据刻度自适应规则生成合适的刻度值
        Long startTimeUsec = YDateUtil.yyyy_MM_dd_HH_mm_ss(config.getStartTime()) * 1000;
        Long endTimeUsec = YDateUtil.yyyy_MM_dd_HH_mm_ss(config.getEndTime()) * 1000;
        if (scaleSelfAdaptive) {
            long timeSpan = endTimeUsec - startTimeUsec;
            if (timeSpan == 0) {
                scale = 1;
            } else {
                for (ScaleSelfAdaptiveRule rule : scaleRuleList) {
                    if (timeSpan > rule.getFrom()) {
                        if (rule.getTo() == null || timeSpan <= rule.getTo()) {
                            scale = rule.getScale();
                            break;
                        }
                    }
                }
            }
        }

        // 3、根据开始结束时间、刻度生成时间轴
        SimpleDateFormat baseSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        SimpleDateFormat sdf = new SimpleDateFormat(labelTimeFormatter);
        long scaleUsec = scale * 1000L;
        List<List<String>> resPointTimeRangeList = new ArrayList<>();
        List<String> pointLabelTimeList = new ArrayList<>();
        List<Long> pointLabelTimeUsecList = new ArrayList<>();
        Long tempTimeUsec = startTimeUsec;
        while (tempTimeUsec <= endTimeUsec) {
            List<String> pointTimeRange = new ArrayList<>();
            pointTimeRange.add(baseSdf.format(tempTimeUsec));
            pointLabelTimeList.add(sdf.format(new Date(tempTimeUsec)));
            pointLabelTimeUsecList.add(tempTimeUsec);
            tempTimeUsec += scaleUsec;
            if (tempTimeUsec <= endTimeUsec) {
                pointTimeRange.add(baseSdf.format(tempTimeUsec));
            } else {
                pointTimeRange.add(baseSdf.format(endTimeUsec));
            }
            resPointTimeRangeList.add(pointTimeRange);
        }

        // 4、生成要合并的字段结果集
        Map<String, List<Double>> mergeFieldMap = new HashMap<>();
        for (MergeField mergeField : mergeFieldList) {
            mergeFieldMap.put(mergeField.getName() + "List", new ArrayList<>());
        }

        // 5、归并数据，默认往前归并
        int pointSize = pointLabelTimeUsecList.size();
        int inx = 0;
        List<String> labelTimeList = new ArrayList<>();
        try {
			for (Object d : data) {
				Object cycleTimeObj;
				if (d instanceof Map) {
					cycleTimeObj = ((Map<?, ?>) d).get(cycleTimeKey);
				} else {
					Field cycleTimeField = clazz.getDeclaredField(cycleTimeKey);
					cycleTimeField.setAccessible(true);
					cycleTimeObj = cycleTimeField.get(d);
				}
				long cycleTimeUsec;
				if (cycleTimeObj instanceof Integer) {
					// 单位秒，转为毫秒
					cycleTimeUsec = ((Integer) cycleTimeObj).longValue() * 1000;
				} else if (cycleTimeObj instanceof Long) {
					// 单位毫秒
					cycleTimeUsec = (Long) cycleTimeObj;
				} else if (cycleTimeObj instanceof String) {
					// 字符串转为毫秒
					cycleTimeUsec = YDateUtil.yyyy_MM_dd_HH_mm_ss(String.valueOf(cycleTimeObj)) * 1000;
				} else if (cycleTimeObj instanceof LocalDateTime) {
					// LocalDateTime类型转为毫秒
					cycleTimeUsec = LocalDateTimeUtil.toEpochMilli(((LocalDateTime) cycleTimeObj));
				} else {
					throw new Exception("不支持的时间周期数据类型");
				}
				while (inx < pointSize && cycleTimeUsec >= pointLabelTimeUsecList.get(inx)) {
					labelTimeList.add(pointLabelTimeList.get(inx));
					mergeFieldMap.forEach((mergeFieldKey, valueList) -> valueList.add(0d));
					inx++;
				}
				int size = labelTimeList.size();
				for (MergeField mergeField : mergeFieldList) {
					if (StrUtil.isNotEmpty(mergeField.getJsScript())) {
						mergeField.setCustomCalculate(true);
					}
					if (mergeField.isCustomCalculate()) {
						continue;
					}
					// 没有指定自定义计算函数
					// 获取值
					String mergeFieldName = mergeField.getName();
					Double value;
					if (d instanceof Map) {
						value = Double.valueOf(String.valueOf(((Map<?, ?>) d).get(mergeFieldName)));
					} else {
						Field field = clazz.getDeclaredField(mergeFieldName);
						field.setAccessible(true);
						value = Double.valueOf(String.valueOf(field.get(d)));
					}
					// 获取已计算值数组
					List<Double> valueList = mergeFieldMap.get(mergeFieldName + "List");
					// 取出最后一个计算值
					Double valueOld = valueList.get(size - 1);
					MergeCalculateType calculateType = mergeField.getCalculateType();
					if (calculateType == null) {
						// 没有指定计算方式，默认相加
						valueList.set(size - 1, valueOld + value);
					} else {
						switch (calculateType) {
							case ADD:
								valueList.set(size - 1, valueOld + value);
								break;
							case MIN:
								valueList.set(size - 1, Math.min(valueOld, value));
								break;
							case MAX:
								valueList.set(size - 1, Math.max(valueOld, value));
								break;
							default:
								break;
						}
					}
				}
			}
        } catch (Exception e) {
            e.printStackTrace();
        }

		// 6、补全时间轴，防止断数据时能补刻度
        while (inx < pointLabelTimeList.size()) {
            labelTimeList.add(pointLabelTimeList.get(inx));
            mergeFieldMap.forEach((mergeFieldKey, valueList) -> valueList.add(0d));
            inx++;
        }

        // 7、处理自定义归并计算函数
		try {
			for (MergeField mergeField : mergeFieldList) {
				ScriptEngine jsEngine;
				Invocable inv = null;
				if (StrUtil.isNotEmpty(mergeField.getJsScript())) {
					jsEngine = new ScriptEngineManager().getEngineByName("js");
					jsEngine.eval(mergeField.getJsScript());
					inv = (Invocable) jsEngine;
				}
				if (mergeField.isCustomCalculate()) {
					for (int i = 0; i < inx; i++) {
						Double val;
						if (inv != null) {
							val = Double.parseDouble(inv.invokeFunction(
									"customCalculate", mergeFieldMap, i, pointLabelTimeUsecList, scale).toString());
						} else {
							val = mergeField.getCustomCalculate().calculate(mergeFieldMap, i, pointLabelTimeUsecList, scale);
						}
						mergeFieldMap.get(mergeField.getName() + "List").set(i, val);
					}
				}
			}
		} catch (ScriptException | NoSuchMethodException e) {
			e.printStackTrace();
		}

        // 8、返回归并结果集
        Map<String, Object> map = new HashMap<>();
        map.put("currentScale", scale);
        map.put("resPointTimeRangeArr", resPointTimeRangeList);
        map.put("resLabelTimeArr", labelTimeList);
        map.put("resFieldMap", mergeFieldMap);
        return map;
    }
}
